package main

import (
    "strings"
    "net/http"
    "time"
    "log"
    "fmt"
    "io/ioutil"
    "encoding/json"
    "sync"
    "flag"
    "os"

    "github.com/zjiecode/wxpusher-client/go/wxpusher"
)

type DoctorInfo struct {
    DoctorCode string
    DeptCodes []string
}

/* ============================================================= [漂亮的分割线 By Adolphus] ============================================================= */
//LOAD_DUTY_DOCTOR RES
type DoctorDutyInfo struct {
    Content DoctorDutyContent `json:"content"`
    Status string `json:"type"`
}

type DoctorDutyContent struct {
    DoctorInfo DutyDoctorInfo `json:"doctorInfo"`
    Doctorls DutyDoctorLs `json:"doctorls"`
    IsShowRegInfo bool `json:"isShowRegInfo"`
}

type DutyDoctorInfo struct {
    DoctorName string `json:"doctorName"`
    DoctorTitle string `json:"doctorTitle"`
}

type DutyDoctorLs struct {
    A []*DutyDoctor `json:"A"`
    B []*DutyDoctor `json:"B"`
    D []*DutyDoctor `json:"D"`
}

type DutyDoctor struct {
    Regs map[string]DutyDoctorRegInfo `json:"regs"`
    DateArr []string `json:"dateArr"`
}

type DutyDoctorRegInfo struct {
    DeptName string `json:"deptName"`
    DeptCode string `json:"deptCode"`
    DoctorCode string `json:"doctorCode"`
    ScheduleDate string `json:"scheduleDate"`
    RegFee float32 `json:"regFee"`
    LeftCount int `json:"leftCount"`
    BranchCode string `json:"branchCode"`
    DeptType string `json:"deptType"`
}
/* ============================================================= [漂亮的分割线 By Adolphus] ============================================================= */
//LOAD_DOCTOR_TIME RES
type DoctorTimeInfo struct {
    Content DoctorTimeContent `json:"content"`
    Status string `json:"type"`
}

type DoctorTimeContent struct {
    DoctorTimes []*DoctorTime `json:"doctorTime"`
}

type DoctorTime struct {
    TotalNum string `json:"totalNum"`
    RegFee string `json:"regFee"`
    BeginTime string `json:"beginTime"`
    WorkStatus string `json:"workStatus"`
    WorkId string `json:"workId"`
    LeftNum string `json:"leftNum"`
    EndTime string `json:"endTime"`
}
/* ============================================================= [漂亮的分割线 By Adolphus] ============================================================= */
//LOAD_DOCTOR_TIME RES
type LockTickerInfo struct {
    Content LockTickerContent `json:"content"`
    Status string `json:"type"`
}

type LockTickerContent struct {
    StateCode string `json:"stateCode"`
    ErrorMSG string `json:"errorMSG"`
}
/* ============================================================= [漂亮的分割线 By Adolphus] ============================================================= */
type TimeCollect struct {
    Dr *DutyDoctorRegInfo
    Dt *DoctorTime
}

const (
    DATE_FORMAT = "2006-01-02"
    TIME_FORMAT = "2006-01-02 15:04:05"
    HOST string = "hw23.yx129.net"
    ORIGIN = "http://" + HOST
    APP_ID string = "wx95a6e0981f0f096e"
    OPEN_ID string = "oocIot0ts4iUell4N3x7-gEfn5Us"
    LOAD_DUTY_DOCTOR = ORIGIN + "/registration/loadDutyDoctor"
    LOAD_DOCTOR_TIME = ORIGIN + "/registration/loadDoctorDetailTime"
    LOCK_TICKER = ORIGIN + "/registration/validateHis"
    COSTIME int = 100
    INTIME int = 2
    APP_TOKEN string = "AT_ee7deABoR656gompF4jeHmranMDdESjx"
    U_ID string = "UID_aX7enqQ563Gg4wwuU1xdbDH61MrH"
)

var (
    wg sync.WaitGroup
    DOCTOR_INFOS = []DoctorInfo{DoctorInfo{DoctorCode:"628", DeptCodes: []string{"61618", "68636"}}}
    TIME_WEIGHT_LIST = []string{"09:00", "09:30", "10:00", "10:30", "11:00", "16:00", "16:30"}
    CARD_NOS = []string{"66580669", "66580668"}

    h bool
    allCost int
    hz int
)

func init() {
    flag.BoolVar(&h, "h", false, "使用帮助")
    flag.IntVar(&allCost, "c", COSTIME, "持续时间长度，单位秒，默认100秒")
    flag.IntVar(&hz, "z", INTIME, "请求时间间隔，单位秒，默认2秒一次")

    flag.Usage = usage
}

func usage() {
    fmt.Fprintf(os.Stderr, `这个是抢中医院许尤佳关于生长发育和
小儿哮喘的就诊号的程序，走到挂号下单部分会通知，
需要到公众号走支付步骤。
v_201912021252

Options:
`)
    flag.PrintDefaults()
}

func main() {
    flag.Parse()

    if h {
        flag.Usage()
    } else {
        sucessChan := make(chan string, 1)
        failChan := make(chan string, 1)
        tms := allCost / hz
        go func() {
            for i := 0; i < tms; i++ {
                wg.Add(1)
                go func() {
                    bodystr := initParams4LoadDutyDoctor()
                    request, err := http.NewRequest("POST", LOAD_DUTY_DOCTOR, strings.NewReader(bodystr))
                    if nil != err {
                        log.Println("[ERROR] err:", err)
                        wg.Done()
                        return
                    }
                    initHeaderInfo(request)
                    doctorDutyInfoRes, err := http.DefaultClient.Do(request)
                    if nil != err {
                        log.Println("[ERROR] doctorDutyInfoRes, err := http.DefaultClient.Do(request) err:", err)
                        wg.Done()
                        return
                    }
                    defer doctorDutyInfoRes.Body.Close()
                    result, err := ioutil.ReadAll(doctorDutyInfoRes.Body)
                    if err != nil {
                        log.Println("[ERROR] ioutil.ReadAll! err", err, "doctorDutyInfoRes.Body:", doctorDutyInfoRes.Body)
                        wg.Done()
                        return
                    }
                    doctorDutyInfo := DoctorDutyInfo{}
                    err = json.Unmarshal(result, &doctorDutyInfo)
                    if nil != err {
                        log.Println("[ERROR] doctorDutyInfo json Unmarshal FAIL! err", err, "result:", string(result))
                        wg.Done()
                        return
                    }
                    if "success" == doctorDutyInfo.Status && doctorDutyInfo.Content.IsShowRegInfo {
                        dutyDoctorRegInfos := []*DutyDoctorRegInfo{}
                        if 0 < len(doctorDutyInfo.Content.Doctorls.A) {
                            dutyDoctorRegInfos = initRegInfos(doctorDutyInfo.Content.Doctorls.A, dutyDoctorRegInfos)
                        }
                        if 0 < len(doctorDutyInfo.Content.Doctorls.B) {
                            dutyDoctorRegInfos = initRegInfos(doctorDutyInfo.Content.Doctorls.B, dutyDoctorRegInfos)
                        }
                        if 0 < len(doctorDutyInfo.Content.Doctorls.D) {
                            dutyDoctorRegInfos = initRegInfos(doctorDutyInfo.Content.Doctorls.D, dutyDoctorRegInfos)
                        }
                        if 0 < len(dutyDoctorRegInfos) {
                            log.Println("Success get doctor duty info. size:", len(dutyDoctorRegInfos))
                            tcsMap := map[string][]*TimeCollect{}
                            for _, dutyDoctorRegInfo := range dutyDoctorRegInfos {
                                if 0 < dutyDoctorRegInfo.LeftCount {
                                    bodystr = initParams4LoadDoctorDetailTime(dutyDoctorRegInfo)
                                    request, err = http.NewRequest("POST", LOAD_DOCTOR_TIME, strings.NewReader(bodystr))
                                    initHeaderInfo(request)
                                    doctorTimeInfoRes, err := http.DefaultClient.Do(request)
                                    if nil != err {
                                        log.Println("[ERROR] doctorTimeInfoRes, err := http.DefaultClient.Do(request) err:", err)
                                        wg.Done()
                                        return
                                    }
                                    defer doctorTimeInfoRes.Body.Close()
                                    result, err = ioutil.ReadAll(doctorTimeInfoRes.Body)
                                    if err != nil {
                                        log.Println("[ERROR] ioutil.ReadAll! err", err, "doctorTimeInfoRes.Body:", doctorTimeInfoRes.Body)
                                        wg.Done()
                                        return
                                    }
                                    doctorTimeInfo := DoctorTimeInfo{}
                                    err = json.Unmarshal(result, &doctorTimeInfo)
                                    if nil != err {
                                        log.Println("[ERROR] doctorTimeInfo json Unmarshal FAIL! err", err, "result:", string(result))
                                        wg.Done()
                                        return
                                    }
                                    if "success" == doctorTimeInfo.Status && 0 < len(doctorTimeInfo.Content.DoctorTimes) {
                                        nohave := true
                                        for _, doctorTime := range doctorTimeInfo.Content.DoctorTimes {
                                            if "" != doctorTime.LeftNum && "0" != doctorTime.LeftNum {
                                                nohave = false
                                                if tcs, ok := tcsMap[doctorTime.BeginTime]; ok {
                                                    tcs = append(tcs, &TimeCollect{Dr: dutyDoctorRegInfo, Dt: doctorTime})
                                                    tcsMap[doctorTime.BeginTime] = tcs
                                                } else {
                                                    tcs = make([]*TimeCollect, 0)
                                                    tcs = append(tcs, &TimeCollect{Dr: dutyDoctorRegInfo, Dt: doctorTime})
                                                    tcsMap[doctorTime.BeginTime] = tcs
                                                }
                                            }
                                        }
                                        if nohave {
                                            log.Println("[WARNING] Get doctor time info empty! result:", string(result), "ScheduleDate:", dutyDoctorRegInfo.ScheduleDate)
                                        }
                                    } else {
                                        log.Println("[WARNING] Fail to get doctor time info! result:", string(result))
                                    }
                                }
                            }
                            if 0 < len(tcsMap) {
                                doctorName := doctorDutyInfo.Content.DoctorInfo.DoctorName
                                doctorTitle := doctorDutyInfo.Content.DoctorInfo.DoctorTitle
                                for _, t := range TIME_WEIGHT_LIST {
                                    if tcs, ok := tcsMap[t]; ok {
                                        for _, tc := range tcs {
                                            if strings.Contains(tc.Dr.DeptName, "发育") || strings.Contains(tc.Dr.DeptName, "哮喘") {
                                                if lockTicker(doctorName, doctorTitle, tc.Dr, tc.Dt) {
                                                    sucessChan <- "success"
                                                    return
                                                }
                                            }
                                        }
                                    }
                                }
                                for _, tcs := range tcsMap {
                                    for _, tc := range tcs {
                                        if strings.Contains(tc.Dr.DeptName, "发育") || strings.Contains(tc.Dr.DeptName, "哮喘") {
                                            if lockTicker(doctorName, doctorTitle, tc.Dr, tc.Dt) {
                                                sucessChan <- "success"
                                                return
                                            }
                                        }
                                    }
                                }
                            } else {
                                log.Println("[WARNING] Get doctor duty info no have ticker! result:", string(result))
                            }
                        } else {
                            log.Println("[WARNING] Get doctor duty info empty! result:", string(result))
                        }
                    } else {
                        log.Println("[WARNING] Fail to get doctor duty info! result:", string(result))
                    }
                    wg.Done()
                }()
                if i + 1 == tms {
                    break
                }
                time.Sleep(time.Duration(hz) * time.Second)
            }
            wg.Wait()
            failChan <- "fail"
        }()
        for {
            select {
            case <-sucessChan:
                sendSuccess()
                log.Println("[INFO] SUCCESS exiting...")
                return
            case <-failChan:
                sendFail()
                log.Println("[INFO] FAIL exiting...")
                return
            default:
            }
        }
    }
}

func initHeaderInfo(r *http.Request) {
    r.Header.Set("Host", HOST)
    r.Header.Set("Accept-Language", "zh-cn")
    r.Header.Set("Origin", ORIGIN)
    r.Header.Set("User-Agent", "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15) AppleWebKit/605.1.15 (KHTML, like Gecko) MicroMessenger/2.3.27(0x12031b10) MacWechat NetType/WIFI WindowsWechat")
    r.Header.Set("Referer", ORIGIN + "/registration/dutyDoctor")
    r.Header.Set("Content-Type", "application/x-www-form-urlencoded;param=value")
}

func initParams4LoadDutyDoctor() string {
    var r http.Request
    currentSeconds := time.Now().Unix()
    currentDate := time.Unix(currentSeconds, 0).Format(DATE_FORMAT)
    after14DaySeconds := currentSeconds + (14 * 24 * 60 * 60);
    after14Day := time.Unix(after14DaySeconds, 0).Format(DATE_FORMAT)
    r.ParseForm()
    r.Form.Add("appId", APP_ID)
    r.Form.Add("openId", OPEN_ID)
    r.Form.Add("doctorCode", DOCTOR_INFOS[0].DoctorCode)
    r.Form.Add("deptCode", DOCTOR_INFOS[0].DeptCodes[0])
    r.Form.Add("deptType", "1")
    r.Form.Add("beginDate", currentDate)
    r.Form.Add("endDate", after14Day)
    return strings.TrimSpace(r.Form.Encode())
}

func initRegInfos(data []*DutyDoctor, dutyDoctorRegInfos []*DutyDoctorRegInfo) []*DutyDoctorRegInfo {
    for _, d := range data {
        for _, key := range d.DateArr {
            if dutyDoctorRegInfo, ok := d.Regs[key]; ok {
                dutyDoctorRegInfos = append(dutyDoctorRegInfos, &dutyDoctorRegInfo)
            }
        }
    }
    return dutyDoctorRegInfos
}

func initParams4LoadDoctorDetailTime(dutyDoctorRegInfo *DutyDoctorRegInfo) string {
    var r http.Request
    currentSeconds := time.Now().Unix()
    currentDate := time.Unix(currentSeconds, 0).Format(DATE_FORMAT)
    r.ParseForm()
    r.Form.Add("appId", APP_ID)
    r.Form.Add("openId", OPEN_ID)
    r.Form.Add("currentDate", currentDate)
    r.Form.Add("beginDate", dutyDoctorRegInfo.ScheduleDate)
    r.Form.Add("endDate", dutyDoctorRegInfo.ScheduleDate)
    r.Form.Add("branchCode", dutyDoctorRegInfo.BranchCode)
    r.Form.Add("deptCode", dutyDoctorRegInfo.DeptCode)
    r.Form.Add("deptType", dutyDoctorRegInfo.DeptType)
    r.Form.Add("doctorCode", dutyDoctorRegInfo.DoctorCode)
    r.Form.Add("regFee", fmt.Sprintf("%f", dutyDoctorRegInfo.RegFee))
    return strings.TrimSpace(r.Form.Encode())
}

func initParams4LockTicker(doctorName, doctorTitle string, dutyDoctorRegInfo *DutyDoctorRegInfo, doctorTime *DoctorTime) string {
    var r http.Request
    currentSeconds := time.Now().Unix()
    currentDate := time.Unix(currentSeconds, 0).Format(DATE_FORMAT)
    r.ParseForm()
    r.Form.Add("appId", APP_ID);
    r.Form.Add("openId", OPEN_ID);
    r.Form.Add("workId", doctorTime.WorkId);
    r.Form.Add("deptType", "1");
    r.Form.Add("deptName", dutyDoctorRegInfo.DeptName)
    r.Form.Add("specialistName", "");
    r.Form.Add("doctorName", doctorName);
    r.Form.Add("doctorTitle", doctorTitle);
    r.Form.Add("currentDate", currentDate);
    r.Form.Add("beginDate", dutyDoctorRegInfo.ScheduleDate)
    r.Form.Add("endDate", dutyDoctorRegInfo.ScheduleDate)
    r.Form.Add("beginTime", doctorTime.BeginTime);
    r.Form.Add("endTime", doctorTime.EndTime);
    r.Form.Add("branchCode", dutyDoctorRegInfo.BranchCode);
    r.Form.Add("deptCode", dutyDoctorRegInfo.DeptCode);
    r.Form.Add("doctorCode", dutyDoctorRegInfo.DoctorCode);
    r.Form.Add("regFee", doctorTime.RegFee);
    r.Form.Add("cardNo", CARD_NOS[0]);
    r.Form.Add("cryptParams", "");
    return strings.TrimSpace(r.Form.Encode())
}

func lockTicker(doctorName, doctorTitle string, dutyDoctorRegInfo *DutyDoctorRegInfo, doctorTime *DoctorTime) bool {
    bodystr := initParams4LockTicker(doctorName, doctorTitle, dutyDoctorRegInfo, doctorTime)
    request, err := http.NewRequest("POST", LOCK_TICKER, strings.NewReader(bodystr))
    initHeaderInfo(request)
    lockTickerInfoRes, err := http.DefaultClient.Do(request)
    if nil != err {
        log.Println("[ERROR] lockTickerInfoRes, err := http.DefaultClient.Do(request) err:", err)
        return false
    }
    defer lockTickerInfoRes.Body.Close()
    result, err := ioutil.ReadAll(lockTickerInfoRes.Body)
    if err != nil {
        log.Println("[ERROR] ioutil.ReadAll! err", err, "lockTickerInfoRes.Body:", lockTickerInfoRes.Body)
        return false
    }
    if strings.Contains(string(result),"success") {
        lockTickerInfo := LockTickerInfo{}
        err = json.Unmarshal(result, &lockTickerInfo)
        if nil != err {
            log.Println("[ERROR] lockTickerInfo json Unmarshal FAIL! err", err, "result:", string(result))
            return false
        }
        if "success" == lockTickerInfo.Status {
            if "0000" == lockTickerInfo.Content.StateCode {
                //锁号成功
                log.Println("[INFO] lock doctor ticker SUCCESS!")
                return true
            }
            //锁号失败
            log.Println("[ERROR] Fail to lock doctor ticker! ErrorMSG:", lockTickerInfo.Content.ErrorMSG)
        }
    }
    log.Println("[ERROR] Fail to lock doctor ticker! result:", string(result))
    return false
}

func sendSuccess() {
    result, err := wxpusher.NewMessage(APP_TOKEN).SetContent("挂号抢票成功，赶紧去支付！").AddUId(U_ID).Send()
    log.Println("result:", result, "err:", err)
}

func sendFail() {
    result, err := wxpusher.NewMessage(APP_TOKEN).SetContent("挂号抢票失败，下次再接再厉！").AddUId(U_ID).Send()
    log.Println("result:", result, "err:", err)
}
